﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.IO;

namespace Matrix_ClassLibrary
{
    //===============================================================//
    // Enum error signal
    //
    public enum Signal
    {
        MultiplyFail,
        SolveMatrixFail,
        SolveMatrixSucess,
    }
    //===============================================================//
    //== Lớp Matrix 
    [Serializable]
    public class Matrix
    {
        int Row, Column;
        int[,] elements;
        public Hashtable testingMatrixColumnPosition;
        /// <summary>
        /// Contructor tạo ma trận 1 dòng, dùng để tạo ma trận 1 x n
        /// </summary>
        /// <param name="text">input đầu vào là chuỗi text dạng nhị phân </param>
        public Matrix(string text)
        {
           
            int count = text.Length;
            this.Row = 1;
            this.Column = count;
            this.elements = new int[1, count];
            for (int i = 0; i < count; i++)
            {
                this.elements[0, i] = Convert.ToInt32(text[i].ToString());
            }
        }
        /// <summary>
        /// Contructor tạo một ma trận với số dòng và cột xác định
        /// </summary>
        /// <param name="row">dòng</param>
        /// <param name="column">cột</param>
        public Matrix(int row, int column) {
            this.Row = row;
            this.Column = column;
            elements = new int[row, column];
        }
        /// <summary>
        /// contructor tạo ma trận khi có một file txt
        /// </summary>
        /// <param name="Path">địa chỉ file txt</param>
        public Matrix(string Path,bool file)
        {
            // Hàm đọc file txt và tạo ma trận
            ReadMatrix(Path);
        }

        ~Matrix()
        {
            this.elements = null;
        }

        #region accestor
        public int column
        {
            get
            {
                return this.Column;
            }
        }
        public int row
        {
            get
            {
                return this.Row;
            }
        }
        public int[,] element
        {
            get {
                return this.elements;
            }
        }
#endregion
        /// <summary>
        /// Hàm đọc file txt để trả về ma trận. File ma trận là file txt chứa ma trận
        /// mỗi phần tử của hàng được cách nhau bằng tab hoặc khoảng trắng
        /// </summary>
        /// <param name="Path">địa chỉ của file ma trận</param>
        private void ReadMatrix(string Path)
        {
            // lấy số dòng và cột
            string[] matrix = File.ReadAllLines(Path);
            this.Row = matrix.Length;
            // tách ký tự tab hoặc khoảng trắng
            this.Column = matrix[0].Split(new string[] { " ", "\t" }, StringSplitOptions.None).Length;
            // tạo một mãng 2 chiều chứa các phần tử ma trận
            this.elements = new int[Row,Column];
            for(int i=0;i<Row;i++)
            {
                // tách ký tự tab hoặc khoảng trắng
                string[] lines = matrix[i].Split(new string[] { " ", "\t" }, StringSplitOptions.None);  // tách mỗi phần tử = khoản trắng
                
                for(int j=0;j<Column;j++)
                {
                    
                    elements[i,j] = Int32.Parse(lines[j]); // gán giá trị và phần tử
                }
            }

        }
        /// <summary>
        /// Hàm cộng 2 ma trậng
        /// </summary>
        /// <param name="b">ma trận cần cộng</param>
        /// <returns></returns>
        public Matrix Addition(Matrix b)
        {
            // kiểm tra 2 ma trận có cùng kích thước không
            if (this.column == b.column && this.row == b.row)
            {
                for (int i = 0; i < this.row; i++)
                    for (int j = 0; j < this.column; j++)
                        // Cộng và modulo cho 2.
                        b.element[i, j] = (b.element[i, j] + this.element[i, j]) % 2;
            }
                // trả về lỗi
            else throw new ArgumentException("2 ma tran khong the cong");
            return b;
        }
        /// <summary>
        /// Kiểm tra xem các phần tử của ma trận có cùng = 0 không.
        /// </summary>
        public bool IsZero
        {
            get
            {
                for (int i = 0; i < this.row; i++)
                    for (int j = 0; j < this.column; j++)
                        if (this.element[i,j] != 0)
                            return false; // nếu có phần tử là khác 0 trả về false
                return true;
            }
        }
        /// <summary>
        /// Trả về khoảng cách hamming của ma trận sinh
        /// </summary>
        public int DistanceHamming
        {
                get
                {
                        //Xác định giá trị u có thể. Ta co 2^sodongmatran U
                    int tongsoU = (int)Math.Pow(2,this.row);
                    String U; // chuỗi U
                    int d,max=this.column; // khoang cách d;
                    Matrix result = new Matrix(1, this.column); // tạo matrix kết quả
                    for (int i = 0; i < tongsoU; i++)
                    {
                        d=0;
                        // Tạo ra dạng nhị phân số đó
                        U = Convert.ToString(i, 2);
                        U = U.PadLeft(this.Row,'0'); // thêm vào cho bên trái các bit 0
                        result = new Matrix(U).Multiply(this); // từ mã đã sinh ra
                        for (int j = 0; j < result.column; j++)
                        {
                            if (result.elements[0, j] == 1) d++;
                        }
                        if (d < max && d!= 0) max = d;
                
                     }
                    return max;
            }
        }
        /// <summary>
        /// Hàm nhân 2 ma trận A * B . dùng cho việc nhân ma trận 1xM với ma trận NxM
        /// Để tạo ra từ mã ta lấy ma trận U (1xN) * ma trận sinh NxM
        /// Hàm này viết trên dạng ma trận tổng quát, áp dụng cho nhân 2 ma trận bất kì
        /// </summary>
        /// <param name="b">ma trận đầu vào phải là ma trận sinh n x m</param>
        /// <returns></returns>
        public Matrix Multiply(Matrix b)
        {
            // ma trận kết quả, có kích thước là dòng ma trận A * cột ma trận B
            Matrix matrix = new Matrix(this.Row, b.Column);
            // Kiểm tra số cột của ma trận được nhân với số dòng ma trận sinh đầu vào
            if (this.Column != b.Row) throw new ArgumentException("Khong the thuc hien nhan 2 ma tran nay");
            for(int i=0; i<this.Row; i++) // duyệt chỉ số dòng từ trên xuống dưới ma trận A
                for (int j = 0; j < b.Column; j++) // duyệt chỉ số từng cột ma trận B 
                {
                    matrix.elements[i, j] = 0; // khởi tạo chỉ số ma trận ket quả
                    for (int k = 0; k < this.Column; k++)
                    {
                         // cộng các phần tử trên dòng thứ i của A với phần các tử trên cột j của B
                         matrix.elements[i,j] += this.elements[i, k]*b.elements[k, j];
                    }
                    matrix.elements[i, j] = matrix.elements[i, j] % 2; // modulo cho 2 trên trườn GF(2)
                }
            return matrix;
        }
        /// <summary>
        /// Ngich đảo ma trận hiện tại
        /// </summary>
        /// <returns></returns>
        public Matrix Transpose()
        {
            // khởi tạo ma trận rỗng.
            Matrix TransposeMatrix = new Matrix(this.Column, this.Row);
            for (int i = 0; i < TransposeMatrix.Row; i++)
                for (int j = 0; j < TransposeMatrix.Column; j++)
                    // nghịch đảo
                    TransposeMatrix.elements[i, j] = this.elements[j, i];
            return TransposeMatrix;
        }
        /// <summary>
        /// Tạo một bản coppy của ma trận hiện tại
        /// </summary>
        public Matrix Clone()
        {
            Matrix matrix = new Matrix(Row, Column);
            for (int i = 0; i < Row; i++)
                for (int j = 0; j < Column; j++)
                    matrix.elements[i, j] = this.elements[i, j];
            return matrix;
        }
        /// <summary>
        /// overide hàm toString.để chuyển ma trận về dạng String
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            if (this.Row == 1)
            {
                for (int i = 0; i < this.Column; i++)
                {
                    sb.Append(elements[0, i].ToString());
                }
            }
            return sb.ToString();
        }
        /// <summary>
        /// Tạo ma trận kiểm tra của ma trận hiện tại n dòng m cột
        /// Ma trận A (nxm).
        /// </summary>
        /// <returns></returns>
        public Matrix MakeTestingMatrix()
        {
            Matrix matrix;
            try
            {
                int j = 0;
                // Tạo một ma trận kết quả với:
                // + Số dòng = số cột ma trận hiện tại - số dòng ma trận hiện tại
                // + Số cột = số cột ma trận hiện tại
                matrix = new Matrix(this.Column - this.Row, this.Column);

                // tạo ma trận đường chéo có các phần tử = 1 ở m cột cuối cùng của ma trận hiện tại
                int Col = this.Column - 1;
                for (int i = matrix.Row - 1; i >= 0; i--)
                {
                    matrix.elements[i, Col] = 1;
                    Col--;
                }

                // một mãng Int dùng để làm tham biến đầu vào để nhận kết quả trả về khi giải ma trận
                // mãng Int này chính là các giá trị 1 dòng của ma trận kiểm tra
                int[] arrInt = new int[this.Column];

                //ta duyệt để tìm ra giá trị từng dòng 1
                for (int i = 0; i < matrix.Row; i++)
                {
                    // trước tiên ta gán các giá trị dòng đã có trong ma trận vào mãng
                    for (j = 0; j < matrix.Column; j++)
                    {
                        arrInt[j] = matrix.elements[i, j];
                    }

                    // tạo một phiên bản coppy ma trận hiện tại
                    Matrix cpMatrix = this.Clone();

                    // giải ma trận 
                    SolveMatrix(cpMatrix.elements, ref arrInt, this.Row, this.Column);

                    // ta lại gán lại giá trị dòng i của ma trận A với kết quả vừa giải;
                    for (j = 0; j < matrix.Column; j++)
                    {
                        matrix.elements[i, j] = arrInt[j];
                    }
                }
            }
            catch
            {
                throw new Exception("Không thể giải được ma trận, yêu cầu nhập ma trận khác");
            }
             // tạo một hastable để lưu vị trí của cột ma trận kiểm tra mới tìm xong
                testingMatrixColumnPosition = new Hashtable(matrix.Column);
                for (int i = 0; i < matrix.Column; i++)
                {
                    string s = string.Empty;
                    for (int j = 0; j < matrix.Row; j++)
                    {
                        s += matrix.elements[j, i].ToString(); // tạo chuỗi cột 
                    }
                     try
                     {
              
                            // add vào hashtable ;
                            testingMatrixColumnPosition.Add(s, i + 1);
                     }
            
                    catch // nếu quá trình add côt vào hastable, nếu có cột nào trùng thì ma trận kiểm sinh này
                            // không thể sử dụng để sửa lỗi.
                        {
                            continue;
                        }
                }   
            // trả về ma trận kiểm tra mới tìm được
            return matrix;
            
        }

        /// <summary>
        /// Hàm swap, dùng để hoán vị phần tử
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        private void Swap(ref int a, ref int b)
        {
            int tmp=a;
            a=b;
            b=tmp;
        }
        /// <summary>
        /// Hàm giải ma trận và trả về một mãng kết quả thông qua tham biến kq
        /// Hàm này chỉ phục vụ cho việc tìm ma trận sinh.
        /// Hàm sử dụng thuật toán Gauss giải hệ phương trình tuyến tính thuần nhất bậc nhất.
        /// Hệ Phương trình này vô số nghiệm với các nghiệm tự chọn nằm ở cuối hệ
        /// </summary>
        /// <param name="A">Mãng 2 chiều các phần tử của ma trận</param>
        /// <param name="kq">mãng kết quả, chính là dòng của ma trận kiểm tra</param>
        /// <param name="n">số dòng của ma trận đầu vào</param>
        /// <param name="m">số cột của ma trận đầu vào</param>
        /// <returns></returns>
        private Signal SolveMatrix(int[,] A, ref int[] kq,int n,int m)
        {
            try
            {
                int i, j, k;
                int tmp;
                // xu ly mảng vào theo hàng
                for (i = 0; i < n; i++)
                {
                    int c = 0; // danh dau hang co gia tri cot thu i = 1 
                    if (A[i, i] == 0) // neu phan tu giua =0 thi tim phan o cac hang duoi co phan tu tai
                    // cot do =1 va hoan vi 
                    {
                        for (j = i + 1; j < n; j++)
                        {
                            if (A[j, i] == 1)
                            {
                                c = j;
                                break;
                            }
                        }
                        if (c != 0)
                        {
                            for (k = 0; k < m; k++)
                            {
                                //Swap(ref A[i, k], ref  A[c, k]);
                                int sw = A[i, k];
                                A[i, k] = A[c, k];
                                A[c, k] = sw;
                            }
                            c = 0;
                        }

                    }
                }
                //triet tieu bien tim he tam giac
                for (k = 0; k < n - 1; k++)
                {
                    int c = 0;
                    if (A[k, k] == 0) //Sau khi xử lý theo hàng, nếu vẫn chưa tạo được ma trận đường
                        //chéo theo ta tiếp tục xử lý ma trận theo cột. Nếu tại cột k, phần tử A[k,k] = 0
                        // ta hoán vị cột đó với các cột khác cùng hàng có giá trị khác 0
                    {
                        for (j = k + 1; j < m; j++)
                        {
                            if (A[k, j] == 1)
                            {
                                c = j;
                                break;
                            }
                        }
                        if (c != 0)
                        {
                            for (i = 0; i < n; i++)
                            {
                                Swap(ref A[i, k], ref  A[i, c]);
                            }
                            c = 0;
                        }

                    }
                    // Khi A[k,k] khác 0 ta tiến hành tìm ma trận bậc thang và giải pt theo pp Gauss
                    for (i = k + 1; i < n; ++i)
                    {
                        int d = A[i, k] / A[k, k];
                        for (j = k; j < m; ++j)
                            A[i, j] = (int)(A[i, j] + A[k, j] * d) % 2;
                    }
                }
                //da tim duoc he tam giac => tinh nghiem
                //neu he so cua xn-1 xap xi = 0 thi he suy bien
                if (Math.Abs(A[n - 1, n - 1]) < 1E-6)
                    return 0;
                else
                {
                    for (i = n - 1; i >= 0; i--)
                    {
                        tmp = 0;
                        for (j = i; j < m; j++)
                            tmp = tmp + A[i, j] * kq[j];
                        kq[i] = tmp % 2;
                    }
                    return Signal.SolveMatrixSucess;
                }
            }
            catch // Nếu có lỗi chia cho zero, ma trận này không giải được, ta trả về
                // một ngoại lệ
            {
                throw new Exception("Ma trận không giải được");
            }

        }


        /// <summary>
        /// Giải mã từ mã từ ma trận sinh
        /// </summary>
        /// <param name="input">Ma trận đầu vào là từ mã nhận được cần giải mã.Ma trận này có 1 dòng</param>
        /// <returns></returns>
        public string decrypt(Matrix input)
        {
            // Mãng int chứa kết quả giải ma trận sinh.
            int[] kq = new int[this.Column];
            // Gán giá trị cho mãng kq từ ma trận đầu vào (ma trận đầu vào là ma trận 1 dòng (1xm))
            for(int i=0;i<input.Column;i++)
                kq[i] = input.elements[0,i];
            // Nghịch đảo ma trận sinh hiện tại
            Matrix matrix = this.Transpose();
            // Hàm giải ma trận và trả về kq qua tham biến
            Solve(matrix.elements, ref kq, matrix.Row, matrix.Column);

           // trả về chuỗi đã giải mã.
            string s="";
            for(int i=0;i<this.Row;i++)
                s+=kq[i].ToString();
            return s;
        }
        /// <summary>
        /// Hàm giải ma trận, Dùng bổ trợ cho Hàm encrypt phía trên
        /// Hàm để giải và trả về mãng kq là chuỗi ban đầu.
        /// Hàm này hoạt động tương tự như SolveMatrix
        /// Solve sẽ thực hiện giải phương trình tuyến tính
        /// </summary>
        /// <param name="A">mãng 2 chiều chứa phần tử ma trận sinh</param>
        /// <param name="kq">mãng 1 chiều chứa kết quả</param>
        /// <param name="n">Số cột ma trận sinh</param>
        /// <param name="m">số dòng ma trận sinh</param>
        /// <returns></returns>
        private Signal Solve(int[,] A, ref int[] kq, int n, int cot)
        {
            int m = cot;
            int i, j, k;
            int tmp;
            //triet tieu bien tim he tam giac
            for (k = 0; k < cot - 1; k++)
            {
                int c = 0;
                if (A[k, k] == 0) // neu phan tu giua =0 thi tim phan o cac hang duoi co phan tu tai
                // cot do =1 va hoan vi 
                {
                    for (j = k + 1; j < n; j++)
                    {
                        if (A[j, k] == 1)
                        {
                            c = j;
                            break;
                        }
                    }
                    // c khác 0, chứng tỏ có hàng thỏa yêu cầu để ta hoán vị
                    if (c != 0)
                    {
                        Swap(ref kq[k], ref kq[c]);
                        for (i = 0; i < cot; i++)
                        {
                            Swap(ref A[k, i], ref  A[c, i]);
                        }
                        c = 0;
                    }

                }
                // tìm ma trận bậc than
                for (i = k + 1; i < n; ++i)
                {
                    int d = A[i, k] / A[k, k];
                    kq[i] = (kq[i] + kq[k] * d) % 2;
                    for (j = k; j < m; ++j)
                        A[i, j] = (int)(A[i, j] + A[k, j] * d) % 2;
                }
            }
            //da tim duoc he tam giac => tinh nghiem
            //neu he so cua xn-1 xap xi = 0 thi he suy bien

            for (i = m - 1; i >= 0; i--)
            {
                tmp = 0;
                for (j = i; j < m; j++)
                    tmp = tmp + A[i, j] * kq[j];
                kq[i] = tmp % 2;
            }
            return Signal.SolveMatrixSucess;

        }
    }
}

   